using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace System.DataStructures
{
	/// <summary>
	/// This is an abstract class for the collection of unique elements of type T
	/// </summary>
	/// <typeparam name="T">The type of items in the specified set.</typeparam>
	/// <remarks>Null is considered the equivalent of the empty set.</remarks>
	[Serializable]
	[DebuggerDisplay("Count = {Count}")]
	internal abstract class SetBase<T> : ISetEx<T>
	{
		public abstract int Count { get; }

		public bool IsEmpty
		{
			get { return (Count == 0); }
		}

		public virtual bool IsReadOnly
		{
			get { return false; }
		}

		public abstract bool Add(T item);

		public abstract void Clear();

		public abstract ISetEx<T> CloneType(IEnumerable<T> initialContents = null);

		public abstract bool Contains(T item);

		public bool ContainsAll(IEnumerable<T> items)
		{
			return (items == null) || items.All(Contains);
		}

		public void CopyTo(T[] array, int index)
		{
			Guard.NotNull(array, () => array);
			if (array.Length - index > Count)
			{
				throw new IndexOutOfRangeException("Copying to the array would exceed the size of the array.");
			}
			foreach (T value in this)
			{
				array[index++] = value;
			}
		}

		public virtual ISetEx<T> Difference(IEnumerable<T> items)
		{
			ISetEx<T> result = CloneType(this);
			if (!items.IsNullOrEmpty())
			{
				result.DifferenceWith(items);
			}
			return result;
		}

		public virtual void DifferenceWith(IEnumerable<T> items)
		{
			if (items == null)
			{
				return;
			}
			List<T> list = items.Distinct().ToList();
			foreach (T item in list)
			{
				Remove(item);
			}
		}

		public void ExceptWith(IEnumerable<T> items)
		{
			DifferenceWith(items);
		}

		public abstract IEnumerator<T> GetEnumerator();

		public ISetEx<T> GetSubset(Predicate<T> predicate)
		{
			return CloneType(this.Where(i => predicate(i)));
		}

		public virtual ISetEx<T> Intersect(IEnumerable<T> items)
		{
			if (items.IsNullOrEmpty())
			{
				return CloneType();
			}
			//clone the shortest, enumerate the longest.
			bool instanceIsBigger = Count >= items.Count();
			ISetEx<T> result = CloneType(instanceIsBigger ? items : this);
			result.IntersectWith(instanceIsBigger ? this : items);
			return result;
		}

		public virtual void IntersectWith(IEnumerable<T> items)
		{
			if (items.IsNullOrEmpty())
			{
				Clear();
				return;
			}
			ICollection<T> keepThese = items.Distinct().ToList();
			IEnumerable<T> removeThese = this.Where(item => !keepThese.Contains(item));
			DifferenceWith(removeThese);
		}

		public bool IsProperSubsetOf(ISetEx<T> set)
		{
			if (set.IsNullOrEmpty())
			{
				return false;
			}
			return IsSubsetOf(set) && !SetEquals(set);
		}

		public bool IsProperSubsetOf(IEnumerable<T> items)
		{
			if (items.IsNullOrEmpty())
			{
				return false;
			}
			ISetEx<T> otherSet = items as ISetEx<T> ?? CloneType(items);
			return IsProperSubsetOf(otherSet);
		}

		public bool IsProperSupersetOf(ISetEx<T> set)
		{
			if (set.IsNullOrEmpty())
			{
				return Count > 0;
			}
			return IsSupersetOf(set) && !SetEquals(set);
		}
		
		public bool IsProperSupersetOf(IEnumerable<T> items)
		{
			if (items.IsNullOrEmpty())
			{
				return Count > 0;
			}
			ISetEx<T> otherSet = items as ISetEx<T> ?? CloneType(items);
			return IsProperSupersetOf(otherSet);
		}

		public bool IsSubsetOf(ISetEx<T> set)
		{
			if (set.IsNullOrEmpty())
			{
				return Count == 0;
			}
			return set.ContainsAll(this);
		}

		public virtual bool IsSubsetOf(IEnumerable<T> items)
		{
			if (items.IsNullOrEmpty())
			{
				return Count == 0;
			}
			ISetEx<T> otherSet = items as ISetEx<T> ?? CloneType(items);
			return IsSubsetOf(otherSet);
		}

		public bool IsSupersetOf(ISetEx<T> set)
		{
			return set.IsNullOrEmpty() || ContainsAll(set);
		}

		public virtual bool IsSupersetOf(IEnumerable<T> items)
		{
			return items.IsNullOrEmpty() || ContainsAll(items);
		}

		public bool Overlaps(IEnumerable<T> items)
		{
			return !items.IsNullOrEmpty() && items.Any(Contains);
		}

		public abstract bool Remove(T item);

		public void RemoveWhere(Predicate<T> predicate)
		{
			DifferenceWith(this.Where(i => predicate(i)));
		}

		public virtual bool SetEquals(IEnumerable<T> items)
		{
			if (items == null)
			{
				return Count == 0;
			}
			int counter = 0;
			foreach (T item in items.Distinct().ToList())
			{
				if (!Contains(item))
				{
					return false;
				}
				counter++;
			}
			return Count == counter;
		}

		public virtual ISetEx<T> SymmetricDifference(IEnumerable<T> items)
		{
			if (items.IsNullOrEmpty())
			{
				return CloneType(this);
			}

			//clone the longest, enumerate the shortest.
			bool instanceIsBigger = Count >= items.Count();
			ISetEx<T> result = CloneType(instanceIsBigger ? this : items);
			result.SymmetricDifferenceWith(instanceIsBigger ? items : this);
			return result;
		}

		public virtual void SymmetricDifferenceWith(IEnumerable<T> items)
		{
			if (items == null)
			{
				return;
			}
			List<T> list = items.Distinct().ToList();
			foreach (T item in list)
			{
				if (Contains(item))
				{
					Remove(item);
				}
				else
				{
					Add(item);
				}
			}
		}

		public void SymmetricExceptWith(IEnumerable<T> items)
		{
			SymmetricDifferenceWith(items);
		}

		public virtual ISetEx<T> Union(IEnumerable<T> items)
		{
			ISetEx<T> result = CloneType(this);
			if (!items.IsNullOrEmpty())
			{
				result.UnionWith(items);
			}
			return result;
		}

		public virtual void UnionWith(IEnumerable<T> items)
		{
			if (items == null)
			{
				return;
			}
			List<T> list = items.Distinct().ToList();
			foreach (T item in list)
			{
				Add(item);
			}
		}

		void ICollection<T>.Add(T item)
		{
			Add(item);
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}
	}
}
